﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 版权所有 (c) 2022 北京超维景生物科技有限公司 保留所有权利。
 * CLR版本：4.0.30319.42000
 * 文件名：Kernel
 * 
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/2/8 15:52:51
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ImageK.Util;

namespace ImageK.IO
{
    /// <summary>
    /// Decodes single and multi-image TIFF files. The LZW decompression
    /// code was contributed by Curtis Rueden.
    /// </summary>
    public class TiffDecoder
    {
		public const int NEW_SUBFILE_TYPE = 254;
		public const int IMAGE_WIDTH = 256;
		public const int IMAGE_LENGTH = 257;
		public const int BITS_PER_SAMPLE = 258;
		public const int COMPRESSION = 259;
		public const int PHOTO_INTERP = 262;
		public const int IMAGE_DESCRIPTION = 270;
		public const int STRIP_OFFSETS = 273;
		public const int ORIENTATION = 274;
		public const int SAMPLES_PER_PIXEL = 277;
		public const int ROWS_PER_STRIP = 278;
		public const int STRIP_BYTE_COUNT = 279;
		public const int X_RESOLUTION = 282;
		public const int Y_RESOLUTION = 283;
		public const int PLANAR_CONFIGURATION = 284;
		public const int RESOLUTION_UNIT = 296;
		public const int SOFTWARE = 305;
		public const int DATE_TIME = 306;
		public const int ARTIST = 315;
		public const int HOST_COMPUTER = 316;
		public const int PREDICTOR = 317;
		public const int COLOR_MAP = 320;
		public const int TILE_WIDTH = 322;
		public const int SAMPLE_FORMAT = 339;
		public const int JPEG_TABLES = 347;
		public const int METAMORPH1 = 33628;
		public const int METAMORPH2 = 33629;
		public const int IPLAB = 34122;
		public const int NIH_IMAGE_HDR = 43314;
		public const int META_DATA_BYTE_COUNTS = 50838; // private tag registered with Adobe
		public const int META_DATA = 50839; // private tag registered with Adobe

		//constants
		static int UNSIGNED = 1;
		static int SIGNED = 2;
		public static int FLOATING_POINT = 3;

		//field types
		public static int SHORT = 3;
		static int LONG = 4;

		// metadata types
		public static int MAGIC_NUMBER = 0x494a494a;  // "IJIJ"
		public static int INFO = 0x696e666f;  // "info" (Info image property)
		public static int LABELS = 0x6c61626c;  // "labl" (slice labels)
		public static int RANGES = 0x72616e67;  // "rang" (display ranges)
		public static int LUTS = 0x6c757473;    // "luts" (channel LUTs)
		public static int PLOT = 0x706c6f74;    // "plot" (serialized plot)
		public static int ROI = 0x726f6920;     // "roi " (ROI)
		public static int OVERLAY = 0x6f766572; // "over" (overlay)
        public static int PROPERTIES = 0x70726f70; // "prop" (properties)

		private string _directory;
		private string _name;
		private string url;
		protected RandomAccessStream _in;
	    protected bool debugMode;
		private bool littleEndian;
		private string dInfo;
		private int ifdCount;
		private int[] metaDataCounts;
		private string _tiffMetadata;
		private int photoInterp;

        public TiffDecoder(string directory, string name)
        {
            if (directory == null)
                directory = "";
            _directory = PathUtil.AddSeparator(directory);
            this._directory = directory;
            this._name = name;
        }

        int GetInt()
        {

            int b1 = _in.Read();
            int b2 = _in.Read();
            int b3 = _in.Read();
            int b4 = _in.Read();
            if (littleEndian)
                return ((b4 << 24) + (b3 << 16) + (b2 << 8) + (b1 << 0));
            else
                return ((b1 << 24) + (b2 << 16) + (b3 << 8) + b4);
        }

        long getUnsignedInt()
        {
            return (long) GetInt()&0xffffffffL;
        }

        int GetShort()
        {
            try
            {
                int b1 = _in.Read();
                int b2 = _in.Read();
                if (littleEndian)
                    return ((b2 << 8) + b1);
                else
                    return ((b1 << 8) + b2);
            }
            catch
            {
                throw new IOException();
            }
        }

        long ReadLong()
        {
            if (littleEndian)
                return ((long)GetInt() & 0xffffffffL) + ((long)GetInt() << 32);
            else
                return ((long)GetInt() << 32) + ((long)GetInt() & 0xffffffffL);
            //return in.read()+(in.read()<<8)+(in.read()<<16)+(in.read()<<24)+(in.read()<<32)+(in.read()<<40)+(in.read()<<48)+(in.read()<<56);
        }

        double ReadDouble()
        {
            return BitConverter.Int64BitsToDouble(ReadLong());
        }

        long OpenImageFileHeader()
        {
            try
            {
                // Open 8-byte Image File Header at start of file.
                // Returns the offset in bytes to the first IFD or -1
                // if this is not a valid tiff file.
                int byteOrder = _in.ReadShort();
                if (byteOrder == 0x4949) // "II"
                    littleEndian = true;
                else if (byteOrder == 0x4d4d) // "MM"
                    littleEndian = false;
                else
                {
                    _in.Close();
                    return -1;
                }

                int magicNumber = GetShort(); // 42
                long offset = ((long)GetInt()) & 0xffffffffL;
                return offset;
            }
            catch
            {
                throw new IOException();
            }
        }

        int GetValue(int fieldType, int count)
        {
            try
            {
                int value = 0;
                int unused;
                if (fieldType == SHORT && count == 1)
                {
                    value = GetShort();
                    unused = GetShort();
                }
                else
                    value = GetInt();

                return value;
            }
            catch
            {
                throw new IOException();
            }
        }

        void getColorMap(long offset, FileInfo fi)
        {
            byte[] colorTable16 = new byte[768 * 2];
            long saveLoc = _in.getLongFilePointer();
            _in.Seek(offset);
            _in.ReadFully(colorTable16);
            _in.Seek(saveLoc);
            fi.lutSize = 256;
            fi.reds = new byte[256];
            fi.greens = new byte[256];
            fi.blues = new byte[256];
            int j = 0;
            if (littleEndian) j++;
            int sum = 0;
            for (int i = 0; i < 256; i++)
            {
                fi.reds[i] = colorTable16[j];
                sum += fi.reds[i];
                fi.greens[i] = colorTable16[512 + j];
                sum += fi.greens[i];
                fi.blues[i] = colorTable16[1024 + j];
                sum += fi.blues[i];
                j += 2;
            }
            if (sum != 0 && fi.fileType == FileInfo.GRAY8)
                fi.fileType = FileInfo.COLOR8;
        }

        byte[] getString(int count, long offset)
        {
            count--; // skip null byte at end of string
            if (count <= 3)
                return null;
            byte[] bytes = new byte[count];
            long saveLoc = _in.getLongFilePointer();
            _in.Seek(offset);
            _in.ReadFully(bytes);
            _in.Seek(saveLoc);
            return bytes;
        }

        /** Save the image description in the specified FileInfo. ImageJ
        saves spatial and density calibration data in this string. For
        stacks, it also saves the number of images to avoid having to
        decode an IFD for each image. */
        public void saveImageDescription(byte[] description, FileInfo fi)
        {
            string id = Encoding.UTF8.GetString(description);
            bool createdByImageJ = id.StartsWith("ImageJ");
            if (!createdByImageJ)
                saveMetadata(GetName(IMAGE_DESCRIPTION), id);
            if (id.Length < 7) return;
            fi.description = id;
            int index1 = id.IndexOf("images=");
            if (index1 > 0 && createdByImageJ && id[7] != '\n')
            {
                int index2 = id.IndexOf("\n", index1);
                if (index2 > 0)
                {
                    string images = id.Substring(index1 + 7, index2);
                    int n = (int)Tools.parseDouble(images, 0.0);
                    if (n > 1 && fi.compression == FileInfo.COMPRESSION_NONE)
                        fi.nImages = n;
                }
            }
        }

        public void saveMetadata(string name, string data)
        {
            if (data == null) return;
            string str = name + ": " + data + "\n";
            if (_tiffMetadata == null)
                _tiffMetadata = str;
            else
                _tiffMetadata += str;
        }

        void error(String message) 
        {
            if (_in!=null) _in.Close();
            throw new IOException(message);
        }

        void skipUnknownType(int first, int last)
        {
            byte[] buffer = new byte[metaDataCounts[first]];
            for (int i = first; i <= last; i++)
            {
                int len = metaDataCounts[i];
                if (len > buffer.Length)
                    buffer = new byte[len];
                _in.ReadFully(buffer, len);
            }
        }

        public void enableDebugging()
        {
            debugMode = true;
        }

        public FileInfo[] getTiffInfo() 
        {
            long ifdOffset;
            var list = new List<FileInfo>();
            if (_in == null)
            {
                //todo:暂时使用FileStream替代java的RandomAccessFile
                //_in = new RandomAccessStream(new RandomAccessFile(new File(directory + name), "r"));
                _in = new RandomAccessStream(new FileStream(_directory + _name, FileMode.Open));
            }

            ifdOffset = OpenImageFileHeader();
            if (ifdOffset<0L) 
            {
                _in.Close();
                return null;
            }
            if (debugMode) dInfo = "\n  " + _name + ": opening\n";
            while (ifdOffset>0L) {
                _in.Seek(ifdOffset);
                FileInfo fi = OpenIFD();
                if (fi!=null) {
                    list.Add(fi);
                    ifdOffset = ((long) GetInt())&0xffffffffL;
                } else
                    ifdOffset = 0L;
                if (debugMode && ifdCount < 10) dInfo += "nextIFD=" + ifdOffset + "\n";
                if (fi != null && fi.nImages > 1)
                    ifdOffset = 0L;   // ignore extra IFDs in ImageJ and NIH Image stacks
            }
            if (list.Count == 0)
            {
                _in.Close();
                return null;
            }
            else
            {
                FileInfo[] info = list.ToArray();
                if (debugMode) info[0].debugInfo = dInfo;
                if (url != null)
                {
                    _in.Seek(0);
                    info[0].inputStream = _in;
                }
                else
                    _in.Close();
                if (info[0].info == null)
                    info[0].info = _tiffMetadata;
                FileInfo fi = info[0];
                if (fi.fileType == FileInfo.GRAY16_UNSIGNED && fi.description == null)
                    fi.lutSize = 0; // ignore troublesome non-ImageJ 16-bit LUTs
                if (debugMode)
                {
                    int n = info.Length;
                    fi.debugInfo += "number of IFDs: " + n + "\n";
                    fi.debugInfo += "offset to first image: " + fi.getOffset() + "\n";
                    fi.debugInfo += "gap between images: " + getGapInfo(info) + "\n";
                    fi.debugInfo += "little-endian byte order: " + fi.intelByteOrder + "\n";
                }
                return info;
            }
        }

        void decodeNIHImageHeader(int offset, FileInfo fi)
        {
            long saveLoc = _in.getLongFilePointer();
		
		    _in.Seek(offset + 12);
            int version = _in.ReadShort();
		
		    _in.Seek(offset + 160);
            double scale = _in.ReadDouble();
            if (version > 106 && scale != 0.0)
            {
                fi.pixelWidth = 1.0 / scale;
                fi.pixelHeight = fi.pixelWidth;
            } 

		    // spatial calibration
		    _in.Seek(offset + 172);
            int units = _in.ReadShort();
            if (version <= 153) units += 5;
            switch (units)
            {
                case 5: fi.unit = "nanometer"; break;
                case 6: fi.unit = "micrometer"; break;
                case 7: fi.unit = "mm"; break;
                case 8: fi.unit = "cm"; break;
                case 9: fi.unit = "meter"; break;
                case 10: fi.unit = "km"; break;
                case 11: fi.unit = "inch"; break;
                case 12: fi.unit = "ft"; break;
                case 13: fi.unit = "mi"; break;
            }

		    // density calibration
		    _in.Seek(offset + 182);
            int fitType = _in.Read();
            int unused = _in.Read();
            int nCoefficients = _in.ReadShort();
            if (fitType == 11)
            {
                fi.calibrationFunction = 21; //Calibration.UNCALIBRATED_OD
                fi.valueUnit = "U. OD";
            }
            else if (fitType >= 0 && fitType <= 8 && nCoefficients >= 1 && nCoefficients <= 5)
            {
                switch (fitType)
                {
                    case 0: fi.calibrationFunction = 0; break; //Calibration.STRAIGHT_LINE
                    case 1: fi.calibrationFunction = 1; break; //Calibration.POLY2
                    case 2: fi.calibrationFunction = 2; break; //Calibration.POLY3
                    case 3: fi.calibrationFunction = 3; break; //Calibration.POLY4
                    case 5: fi.calibrationFunction = 4; break; //Calibration.EXPONENTIAL
                    case 6: fi.calibrationFunction = 5; break; //Calibration.POWER
                    case 7: fi.calibrationFunction = 6; break; //Calibration.LOG
                    case 8: fi.calibrationFunction = 10; break; //Calibration.RODBARD2 (NIH Image)
                }
                fi.coefficients = new double[nCoefficients];
                for (int i = 0; i < nCoefficients; i++)
                {
                    fi.coefficients[i] = _in.ReadDouble();
                }
			    _in.Seek(offset + 234);
                int size = _in.Read();
                StringBuilder sb = new StringBuilder();
                if (size >= 1 && size <= 16)
                {
                    for (int i = 0; i < size; i++)
                        sb.Append((char)(_in.Read()));
                    fi.valueUnit = sb.ToString();
                }
                else
                    fi.valueUnit = " ";
            }
			
		    _in.Seek(offset + 260);
            int nImages = _in.ReadShort();
            if (nImages >= 2 && (fi.fileType == FileInfo.GRAY8 || fi.fileType == FileInfo.COLOR8))
            {
                fi.nImages = nImages;
                fi.pixelDepth = _in.ReadFloat(); //SliceSpacing
                int skip = _in.ReadShort();      //CurrentSlice
                fi.frameInterval = _in.ReadFloat();
            }
			
		    _in.Seek(offset + 272);
            float aspectRatio = _in.ReadFloat();
            if (version > 140 && aspectRatio != 0.0)
                fi.pixelHeight = fi.pixelWidth / aspectRatio;
		
		    _in.Seek(saveLoc);
        }

        void dumpTag(int tag, int count, int value, FileInfo fi)
        {
            long lvalue = ((long)value) & 0xffffffffL;
            String name = GetName(tag);
            String cs = (count == 1) ? "" : ", count=" + count;
            dInfo += "    " + tag + ", \"" + name + "\", value=" + lvalue + cs + "\n";
            //ij.IJ.log(tag + ", \"" + name + "\", value=" + value + cs + "\n");
        }

        string GetName(int tag)
        {
            string name;
            switch (tag)
            {
                case NEW_SUBFILE_TYPE: name = "NewSubfileType"; break;
                case IMAGE_WIDTH: name = "ImageWidth"; break;
                case IMAGE_LENGTH: name = "ImageLength"; break;
                case STRIP_OFFSETS: name = "StripOffsets"; break;
                case ORIENTATION: name = "Orientation"; break;
                case PHOTO_INTERP: name = "PhotoInterp"; break;
                case IMAGE_DESCRIPTION: name = "ImageDescription"; break;
                case BITS_PER_SAMPLE: name = "BitsPerSample"; break;
                case SAMPLES_PER_PIXEL: name = "SamplesPerPixel"; break;
                case ROWS_PER_STRIP: name = "RowsPerStrip"; break;
                case STRIP_BYTE_COUNT: name = "StripByteCount"; break;
                case X_RESOLUTION: name = "XResolution"; break;
                case Y_RESOLUTION: name = "YResolution"; break;
                case RESOLUTION_UNIT: name = "ResolutionUnit"; break;
                case SOFTWARE: name = "Software"; break;
                case DATE_TIME: name = "DateTime"; break;
                case ARTIST: name = "Artist"; break;
                case HOST_COMPUTER: name = "HostComputer"; break;
                case PLANAR_CONFIGURATION: name = "PlanarConfiguration"; break;
                case COMPRESSION: name = "Compression"; break;
                case PREDICTOR: name = "Predictor"; break;
                case COLOR_MAP: name = "ColorMap"; break;
                case SAMPLE_FORMAT: name = "SampleFormat"; break;
                case JPEG_TABLES: name = "JPEGTables"; break;
                case NIH_IMAGE_HDR: name = "NIHImageHeader"; break;
                case META_DATA_BYTE_COUNTS: name = "MetaDataByteCounts"; break;
                case META_DATA: name = "MetaData"; break;
                default: name = "???"; break;
            }
            return name;
        }

        double getRational(long loc)
        {
            long saveLoc = _in.getLongFilePointer();
                _in.Seek(loc);
            double numerator = getUnsignedInt();
            double denominator = getUnsignedInt();
            _in.Seek(saveLoc);
            if (denominator!=0.0)
                return numerator/denominator;
            else
                return 0.0;
        }

        FileInfo OpenIFD()
        {
			// Get Image File Directory data
			int tag, fieldType, count, value;
			int nEntries = GetShort();
			if (nEntries < 1 || nEntries > 1000)
				return null;
			ifdCount++;
			if ((ifdCount % 50) == 0 && ifdCount > 0)
				IJ.ShowStatus("Opening IFDs: " + ifdCount);
			FileInfo fi = new FileInfo();
			fi.fileType = FileInfo.BITMAP;  //BitsPerSample defaults to 1
			for (int i = 0; i < nEntries; i++)
			{
				tag = GetShort();
				fieldType = GetShort();
				count = GetInt();
				value = GetValue(fieldType, count);
				long lvalue = ((long)value) & 0xffffffffL;
				if (debugMode && ifdCount < 10) dumpTag(tag, count, value, fi);
				switch (tag)
				{
					case IMAGE_WIDTH:
						fi.width = value;
						fi.intelByteOrder = littleEndian;
						break;
					case IMAGE_LENGTH:
						fi.height = value;
						break;
                    case STRIP_OFFSETS:
                    {
                        if (count == 1)
                            fi.stripOffsets = new int[] { value };
                        else
                        {
                            long saveLoc = _in.getLongFilePointer();
                            _in.Seek(lvalue);
                            fi.stripOffsets = new int[count];
                            for (int c = 0; c < count; c++)
                                fi.stripOffsets[c] = GetInt();
                            _in.Seek(saveLoc);
                        }

                        fi.offset = count > 0 ? fi.stripOffsets[0] : value;
                        if (count > 1 && (((long)fi.stripOffsets[count - 1]) & 0xffffffffL) <
                            (((long)fi.stripOffsets[0]) & 0xffffffffL))
                            fi.offset = fi.stripOffsets[count - 1];
                        break;
                    }
                    case STRIP_BYTE_COUNT:
                    {
                        if (count == 1)
                            fi.stripLengths = new int[] { value };
                        else
                        {
                            long saveLoc = _in.getLongFilePointer();
                                _in.Seek(lvalue);
                            fi.stripLengths = new int[count];
                            for (int c = 0; c < count; c++)
                            {
                                if (fieldType == SHORT)
                                    fi.stripLengths[c] = GetShort();
                                else
                                    fi.stripLengths[c] = GetInt();
                            }
                            _in.Seek(saveLoc);
                        }

                        break;
                    }
                    case PHOTO_INTERP:
						photoInterp = value;
						fi.whiteIsZero = value == 0;
						break;
                    case BITS_PER_SAMPLE:
                    {
                        if (count == 1)
                        {
                            if (value == 8)
                                fi.fileType = FileInfo.GRAY8;
                            else if (value == 16)
                                fi.fileType = FileInfo.GRAY16_UNSIGNED;
                            else if (value == 32)
                                fi.fileType = FileInfo.GRAY32_INT;
                            else if (value == 12)
                                fi.fileType = FileInfo.GRAY12_UNSIGNED;
                            else if (value == 1)
                                fi.fileType = FileInfo.BITMAP;
                            else
                                error("Unsupported BitsPerSample: " + value);
                        }
                        else if (count > 1)
                        {
                            long saveLoc = _in.getLongFilePointer();
                                _in.Seek(lvalue);
                            int bitDepth = GetShort();
                            if (bitDepth == 8)
                                fi.fileType = FileInfo.GRAY8;
                            else if (bitDepth == 16)
                                fi.fileType = FileInfo.GRAY16_UNSIGNED;
                            else
                                error("ImageJ cannot open interleaved " + bitDepth + "-bit images.");
                            _in.Seek(saveLoc);
                        }

                        break;
                    }
                    case SAMPLES_PER_PIXEL:
						fi.samplesPerPixel = value;
						if (value == 3 && fi.fileType == FileInfo.GRAY8)
							fi.fileType = FileInfo.RGB;
						else if (value == 3 && fi.fileType == FileInfo.GRAY16_UNSIGNED)
							fi.fileType = FileInfo.RGB48;
						else if (value == 4 && fi.fileType == FileInfo.GRAY8)
							fi.fileType = photoInterp == 5 ? FileInfo.CMYK : FileInfo.ARGB;
						else if (value == 4 && fi.fileType == FileInfo.GRAY16_UNSIGNED)
						{
							fi.fileType = FileInfo.RGB48;
							if (photoInterp == 5)  //assume cmyk
								fi.whiteIsZero = true;
						}
						break;
					case ROWS_PER_STRIP:
						fi.rowsPerStrip = value;
						break;
					case X_RESOLUTION:
						double xScale = getRational(lvalue);
						if (xScale != 0.0) fi.pixelWidth = 1.0 / xScale;
						break;
					case Y_RESOLUTION:
						double yScale = getRational(lvalue);
						if (yScale != 0.0) fi.pixelHeight = 1.0 / yScale;
						break;
					case RESOLUTION_UNIT:
						if (value == 1 && fi.unit == null)
							fi.unit = " ";
						else if (value == 2)
						{
							if (fi.pixelWidth == 1.0 / 72.0)
							{
								fi.pixelWidth = 1.0;
								fi.pixelHeight = 1.0;
							}
							else
								fi.unit = "inch";
						}
						else if (value == 3)
							fi.unit = "cm";
						break;
					case PLANAR_CONFIGURATION:  // 1=chunky, 2=planar
						if (value == 2 && fi.fileType == FileInfo.RGB48)
							fi.fileType = FileInfo.RGB48_PLANAR;
						else if (value == 2 && fi.fileType == FileInfo.RGB)
							fi.fileType = FileInfo.RGB_PLANAR;
						else if (value != 2 && !(fi.samplesPerPixel == 1 || fi.samplesPerPixel == 3 || fi.samplesPerPixel == 4))
						{
							String msg = "Unsupported SamplesPerPixel: " + fi.samplesPerPixel;
							error(msg);
						}
						break;
					case COMPRESSION:
						if (value == 5)
						{// LZW compression
							fi.compression = FileInfo.LZW;
							if (fi.fileType == FileInfo.GRAY12_UNSIGNED)
								error("ImageJ cannot open 12-bit LZW-compressed TIFFs");
						}
						else if (value == 32773)  // PackBits compression
							fi.compression = FileInfo.PACK_BITS;
						else if (value == 32946 || value == 8) //8=Adobe deflate
							fi.compression = FileInfo.ZIP;
						else if (value != 1 && value != 0 && !(value == 7 && fi.width < 500))
						{
							// don't abort with Spot camera compressed (7) thumbnails
							// otherwise, this is an unknown compression type
							fi.compression = FileInfo.COMPRESSION_UNKNOWN;
							error("ImageJ cannot open TIFF files " +
								"compressed in this fashion (" + value + ")");
						}
						break;
					case SOFTWARE:
					case DATE_TIME:
					case HOST_COMPUTER:
					case ARTIST:
						if (ifdCount == 1)
						{
							byte[] bytes = getString(count, lvalue);
							string s = bytes != null ? Encoding.UTF8.GetString(bytes) : null;
							saveMetadata(GetName(tag), s);
						}
						break;
					case PREDICTOR:
						if (value == 2)
						{
							if (fi.compression == FileInfo.LZW)
								fi.compression = FileInfo.LZW_WITH_DIFFERENCING;
							else if (fi.compression == FileInfo.ZIP)
								fi.compression = FileInfo.ZIP_WITH_DIFFERENCING;
						}
						else if (value == 3)
							IJ.log("TiffDecoder: unsupported predictor value of 3");
						break;
					case COLOR_MAP:
						if (count == 768)
							getColorMap(lvalue, fi);
						break;
					case TILE_WIDTH:
						error("ImageJ cannot open tiled TIFFs.\nTry using the Bio-Formats plugin.");
						break;
					case SAMPLE_FORMAT:
						if (fi.fileType == FileInfo.GRAY32_INT && value == FLOATING_POINT)
							fi.fileType = FileInfo.GRAY32_FLOAT;
						if (fi.fileType == FileInfo.GRAY16_UNSIGNED)
						{
							if (value == SIGNED)
								fi.fileType = FileInfo.GRAY16_SIGNED;
							if (value == FLOATING_POINT)
								error("ImageJ cannot open 16-bit float TIFFs");
						}
						break;
					case JPEG_TABLES:
						if (fi.compression == FileInfo.JPEG)
							error("Cannot open JPEG-compressed TIFFs with separate tables");
						break;
					case IMAGE_DESCRIPTION:
						if (ifdCount == 1)
						{
							byte[] s = getString(count, lvalue);
							if (s != null) saveImageDescription(s, fi);
						}
						break;
					case ORIENTATION:
						fi.nImages = 0; // file not created by ImageJ so look at all the IFDs
						break;
					case METAMORPH1:
					case METAMORPH2:
						if ((_name.IndexOf(".STK") > 0 || _name.IndexOf(".stk") > 0) && fi.compression == FileInfo.COMPRESSION_NONE)
						{
							if (tag == METAMORPH2)
								fi.nImages = count;
							else
								fi.nImages = 9999;
						}
						break;
					case IPLAB:
						fi.nImages = value;
						break;
					case NIH_IMAGE_HDR:
						if (count == 256)
							decodeNIHImageHeader(value, fi);
						break;
                    case META_DATA_BYTE_COUNTS:
                    {
                        long saveLoc = _in.getLongFilePointer();
                            _in.Seek(lvalue);
                        metaDataCounts = new int[count];
                        for (int c = 0; c < count; c++)
                            metaDataCounts[c] = GetInt();
                        _in.Seek(saveLoc);
                        break;
                    }
                    case META_DATA:
						getMetaData(value, fi);
						break;
                    default:
                    {
                        if (tag > 10000 && tag < 32768 && ifdCount > 1)
                            return null;
                        break;
                    }
				}
			}
			fi.fileFormat = FileInfo.TIFF;
			fi.fileName = _name;
			fi.directory = _directory;
			if (url != null)
				fi.url = url;
			return fi;
		}

        void getMetaData(int loc, FileInfo fi)
        {
            if (metaDataCounts == null || metaDataCounts.Length == 0)
                return;
            int maxTypes = 10;
            long saveLoc = _in.getLongFilePointer();
		    _in.Seek(loc);
            int n = metaDataCounts.Length;
            int hdrSize = metaDataCounts[0];
            if (hdrSize < 12 || hdrSize > 804)
            {
			    _in.Seek(saveLoc);
                return;
            }
            int magicNumber = GetInt();
            if (magicNumber != MAGIC_NUMBER)
            { // "IJIJ"
			    _in.Seek(saveLoc);
                return;
            }
            int nTypes = (hdrSize - 4) / 8;
            int[] types = new int[nTypes];
            int[] counts = new int[nTypes];
            if (debugMode)
            {
                dInfo += "Metadata:\n";
                dInfo += "   Entries: " + (metaDataCounts.Length - 1) + "\n";
                dInfo += "   Types: " + nTypes + "\n";
            }
            int extraMetaDataEntries = 0;
            int index = 1;
            for (int i = 0; i < nTypes; i++)
            {
                types[i] = GetInt();
                counts[i] = GetInt();
                if (types[i] < 0xffffff)
                    extraMetaDataEntries += counts[i];
                if (debugMode)
                {
                    String id = "unknown";
                    if (types[i] == INFO) id = "Info property";
                    if (types[i] == LABELS) id = "slice labels";
                    if (types[i] == RANGES) id = "display ranges";
                    if (types[i] == LUTS) id = "luts";
                    if (types[i] == PLOT) id = "plot";
                    if (types[i] == ROI) id = "roi";
                    if (types[i] == OVERLAY) id = "overlay";
                    if (types[i] == PROPERTIES) id = "properties";
                    int len = metaDataCounts[index];
                    int count = counts[i];
                    index += count;
                    if (index >= metaDataCounts.Length) index = 1;
                    String lenstr = count == 1 ? ", length=" : ", length[0]=";
                    dInfo += "   " + i + ", type=" + id + ", count=" + count + lenstr + len + "\n";
                }
            }
            fi.metaDataTypes = new int[extraMetaDataEntries];
            fi.metaData = new byte[extraMetaDataEntries][];
            int start = 1;
            int eMDindex = 0;
            for (int i = 0; i < nTypes; i++)
            {
                if (types[i] == INFO)
                    getInfoProperty(start, fi);
                else if (types[i] == LABELS)
                    getSliceLabels(start, start + counts[i] - 1, fi);
                else if (types[i] == RANGES)
                    getDisplayRanges(start, fi);
                else if (types[i] == LUTS)
                    getLuts(start, start + counts[i] - 1, fi);
                else if (types[i] == PLOT)
                    getPlot(start, fi);
                else if (types[i] == ROI)
                    getRoi(start, fi);
                else if (types[i] == OVERLAY)
                    getOverlay(start, start + counts[i] - 1, fi);
                else if (types[i] == PROPERTIES)
                    getProperties(start, start + counts[i] - 1, fi);
                else if (types[i] < 0xffffff)
                {
                    for (int j = start; j < start + counts[i]; j++)
                    {
                        int len = metaDataCounts[j];
                        fi.metaData[eMDindex] = new byte[len]; 
					    _in.ReadFully(fi.metaData[eMDindex], len);
                        fi.metaDataTypes[eMDindex] = types[i];
                        eMDindex++;
                    }
                }
                else
                    skipUnknownType(start, start + counts[i] - 1);
                start += counts[i];
            }
		    _in.Seek(saveLoc);
        }

        void getInfoProperty(int first, FileInfo fi)
        {
            int len = metaDataCounts[first];
            byte[] buffer = new byte[len];
            _in.ReadFully(buffer, len);
            len /= 2;
            char[] chars = new char[len];
            if (littleEndian)
            {
                for (int j = 0, k = 0; j < len; j++)
                    chars[j] = (char)(buffer[k++] & 255 + ((buffer[k++] & 255) << 8));
            }
            else
            {
                for (int j = 0, k = 0; j < len; j++)
                    chars[j] = (char)(((buffer[k++] & 255) << 8) + (buffer[k++] & 255));
            }
            fi.info = new String(chars);
        }

        void getSliceLabels(int first, int last, FileInfo fi)
        {
            fi.sliceLabels = new String[last - first + 1];
            int index = 0;
            byte[] buffer = new byte[metaDataCounts[first]];
            for (int i = first; i <= last; i++)
            {
                int len = metaDataCounts[i];
                if (len > 0)
                {
                    if (len > buffer.Length)
                        buffer = new byte[len];
                    _in.ReadFully(buffer, len);
                    len /= 2;
                    char[] chars = new char[len];
                    if (littleEndian)
                    {
                        for (int j = 0, k = 0; j < len; j++)
                            chars[j] = (char)(buffer[k++] & 255 + ((buffer[k++] & 255) << 8));
                    }
                    else
                    {
                        for (int j = 0, k = 0; j < len; j++)
                            chars[j] = (char)(((buffer[k++] & 255) << 8) + (buffer[k++] & 255));
                    }
                    fi.sliceLabels[index++] = new String(chars);
                    //ij.IJ.log(i+"  "+fi.sliceLabels[i-1]+"  "+len);
                }
                else
                    fi.sliceLabels[index++] = null;
            }
        }

        void getDisplayRanges(int first, FileInfo fi)
        {
            int n = metaDataCounts[first] / 8;
            fi.displayRanges = new double[n];
            for (int i = 0; i < n; i++)
                fi.displayRanges[i] = ReadDouble();
        }

        void getLuts(int first, int last, FileInfo fi)
        {
            fi.channelLuts = new byte[last - first + 1][];
            int index = 0;
            for (int i = first; i <= last; i++)
            {
                int len = metaDataCounts[i];
                fi.channelLuts[index] = new byte[len];
                _in.ReadFully(fi.channelLuts[index], len);
                index++;
            }
        }

        void getRoi(int first, FileInfo fi)
        {
            int len = metaDataCounts[first];
            fi.roi = new byte[len]; 
            _in.ReadFully(fi.roi, len);
        }

        void getPlot(int first, FileInfo fi)
        {
            int len = metaDataCounts[first];
            fi.plot = new byte[len];
            _in.ReadFully(fi.plot, len);
        }

        void getOverlay(int first, int last, FileInfo fi)
        {
            fi.overlay = new byte[last - first + 1][];
            int index = 0;
            for (int i = first; i <= last; i++)
            {
                int len = metaDataCounts[i];
                fi.overlay[index] = new byte[len];
                _in.ReadFully(fi.overlay[index], len);
                index++;
            }
        }

        void getProperties(int first, int last, FileInfo fi)
        {
            fi.properties = new string[last - first + 1];
            int index = 0;
            byte[] buffer = new byte[metaDataCounts[first]];
            for (int i = first; i <= last; i++)
            {
                int len = metaDataCounts[i];
                if (len > buffer.Length)
                    buffer = new byte[len];
                _in.ReadFully(buffer, len);
                len /= 2;
                char[] chars = new char[len];
                if (littleEndian)
                {
                    for (int j = 0, k = 0; j < len; j++)
                        chars[j] = (char)(buffer[k++] & 255 + ((buffer[k++] & 255) << 8));
                }
                else
                {
                    for (int j = 0, k = 0; j < len; j++)
                        chars[j] = (char)(((buffer[k++] & 255) << 8) + (buffer[k++] & 255));
                }
                fi.properties[index++] = new String(chars);
            }
        }

        string getGapInfo(FileInfo[] fi)
        {
            if (fi.Length < 2) return "0";
            long minGap = long.MaxValue;
            long maxGap = -long.MaxValue;
            for (int i = 1; i < fi.Length; i++)
            {
                long gap = fi[i].getOffset() - fi[i - 1].getOffset();
                if (gap < minGap) minGap = gap;
                if (gap > maxGap) maxGap = gap;
            }
            long imageSize = fi[0].width * fi[0].height * fi[0].getBytesPerPixel();
            minGap -= imageSize;
            maxGap -= imageSize;
            if (minGap == maxGap)
                return "" + minGap;
            else
                return "varies (" + minGap + " to " + maxGap + ")";
        }
    }
}
